#include "data_cast.hpp"
#include <string>
#include <type_traits>
#include <utility>
#include "content/data_cast.hpp"
#include "content/data_provider.hpp"
#include "content/data_provider_impl.hpp"
#include "core/assert.hpp"
#include "utils/safe_value.hpp"

using cast_op = void (*) (const void *, void *);

template <typename T>
    requires std::is_enum_v<T>
constexpr std::underlying_type_t<T> enum_value (T e)
{
    return std::underlying_type_t<T> (e);
}

constexpr auto dimm = enum_value (value_type::COUNT);

static cast_op aviable_casts[dimm][dimm];

template <typename From, typename To>
static inline void number_cast (const From *from, To *to)
{
    *to = (To) *from;
}

template <typename Number>
static inline void number_to_string (const Number *from, std::string *to)
{
    *to = std::to_string (*from);
}

#define STRING_CAST_TO(type, fn, ...)                                           \
    static inline void std_string_to_##type (const std::string *from, type *to) \
    {                                                                           \
        char *end = nullptr;                                                    \
        type  temp;                                                             \
        temp = (type) fn (from->c_str (), &end, ##__VA_ARGS__);                 \
        if (end)                                                                \
            *to = temp;                                                         \
    }

static inline void std_string_to_bool(const std::string *from, bool *to) {
    *to = parse_bool(*from);
}

#define STRING_CAST_TO_NUMBERS()      \
    STRING_CAST_TO (u8, strtoull, 0)  \
    STRING_CAST_TO (u16, strtoull, 0) \
    STRING_CAST_TO (u32, strtoull, 0) \
    STRING_CAST_TO (u64, strtoull, 0) \
    STRING_CAST_TO (s8, strtoll, 0)   \
    STRING_CAST_TO (s16, strtoll, 0)  \
    STRING_CAST_TO (s32, strtoll, 0)  \
    STRING_CAST_TO (s64, strtoll, 0)  \
    STRING_CAST_TO (f32, strtod)      \
    STRING_CAST_TO (f64, strtod)

STRING_CAST_TO_NUMBERS ()

static inline void c_string_to_std_string(const c_string_t *from, std::string *to) {
    *to = std::string{from->buff};
}

bool can_cast (value_type t1, value_type t2)
{
    // init cast matrix
    static struct cast_map_init {
        cast_map_init ()
        {
#define CAST_OP(from, to, proc) aviable_casts[enum_value (from)][enum_value (to)] = (cast_op) &proc
#define CAST(from, to, fn) CAST_OP ((value_type) value_traits<from>::store_type, (value_type) value_traits<to>::store_type, (fn<from, to>) )
#define NUMBER_CASTS(from)                                                                                     \
    do                                                                                                         \
    {                                                                                                          \
        CAST (from, u8, number_cast);                                                                          \
        CAST (from, u16, number_cast);                                                                         \
        CAST (from, u32, number_cast);                                                                         \
        CAST (from, u64, number_cast);                                                                         \
        CAST (from, s8, number_cast);                                                                          \
        CAST (from, s16, number_cast);                                                                         \
        CAST (from, s32, number_cast);                                                                         \
        CAST (from, s64, number_cast);                                                                         \
        CAST (from, f32, number_cast);                                                                         \
        CAST (from, f64, number_cast);                                                                         \
        CAST (from, bool, number_cast);                                                                        \
        CAST_OP ((value_type) value_traits<from>::store_type, value_type::STD_STRING, number_to_string<from>); \
    } while (0)
#define STR_TO_NUMBER(to) CAST_OP (value_type::STD_STRING, (value_type) value_traits<to>::store_type, std_string_to_##to)
            // number types and cast to string
            NUMBER_CASTS (u8);
            NUMBER_CASTS (u16);
            NUMBER_CASTS (u32);
            NUMBER_CASTS (u64);
            NUMBER_CASTS (s8);
            NUMBER_CASTS (s16);
            NUMBER_CASTS (s32);
            NUMBER_CASTS (s64);
            NUMBER_CASTS (f32);
            NUMBER_CASTS (f64);
            NUMBER_CASTS (bool);

            // string types to number
            STR_TO_NUMBER (u8);
            STR_TO_NUMBER (u16);
            STR_TO_NUMBER (u32);
            STR_TO_NUMBER (u64);
            STR_TO_NUMBER (s8);
            STR_TO_NUMBER (s16);
            STR_TO_NUMBER (s32);
            STR_TO_NUMBER (s64);
            STR_TO_NUMBER (f32);
            STR_TO_NUMBER (f64);
            CAST_OP(value_type::STD_STRING, value_type::BOOL, std_string_to_bool);

            // c_string to std::string
            CAST_OP(value_type::C_STRING, value_type::STD_STRING, c_string_to_std_string);

#undef NUMBER_CASTS
#undef CAST
#undef CAST_OP
        }
    } _;

    PI_ASSERT (t1 < value_type::COUNT && t2 < value_type::COUNT);
    return aviable_casts[enum_value (t1)][enum_value (t2)] != nullptr;
}

void do_cast (const void *src_v, value_type src_t, void *dst_v, value_type dst_t)
{
    // not check exists
    aviable_casts[enum_value (src_t)][enum_value (dst_t)](src_v, dst_v);
}
